Continuando desde la [segunda parte](/posts/Clasificaci%C3%B3n-mensajes-correo-debian-parte-2) donde ya hemos entrenado y obtenido modelos para la clasificación de mensaje ahora continuaremos a "exportarlos" para poder usarlos:

![Machine learning d2](/static/imgs/posts/MachineLearningD2.png)

Aplicaremos nuevos mensajes de texto al modelo ya generado y este va a devolver predicciones. En este caso cuando introduzcamos un mensaje de texto, usando el modelo se predice a qué categoría de mensajes de la lista de correos de debian pertenece ese mensaje de texto.

### Exportando el modelo

En el *notebook* de la parte 2 ([debian-2015-2019-NaiveBayes-RandomForest.ipynb](https://gitlab.com/strysg/ejercicios-data-science/-/blob/master/curso4/proyecto/NaiveBayes/debian-2015-2019-NaiveBayes-RandomForest.ipynb)) hay una parte que guarda guarda tres cosas:

- El modelo generado aplicando la técnica.
- La matriz *CountVectorizer* con el conteo de palabras y ocurrencias.
- El diccionario de categorías y su valor numérico tal y como lo manejamos para entrenar el modelo.

Con estos objetos ya obtenidos al hacer el entrenamiento, usaremos la biblioteca python [*pickle*](https://docs.python.org/3/library/pickle.html) que serializa objetos python y los guarda en archivos binarios, esos archivos luego se pueden leer desde otro programa o script python y se puede recuperar el objeto python original. 

El proceso de guardado se hace en el con:

    :::python
    import pickle
    pickle.dump(model_multinomialNB, open('./model_multinomialNB.pkl', "wb"))
    pickle.dump(cv_multinomialNB, open('./cv_multinomialNB.pkl', "wb"))
    pickle.dump(labelsEncodedNB, open('./labelsEncodedNB.pkl', "wb"))

    pickle.dump(model_randomForest4, open('./model_randomForest.pkl', "wb"))
    pickle.dump(cv_randomForest4, open('./cv_randomForest.pkl', "wb"))
    pickle.dump(labelsEncodedRf4, open('./labelsEncodedRf.pkl', "wb"))

Que guarda en archivos con extensión `.pkl`. Algo interesante es que los archivos guardados tienen tamaños variables:

    2,3M       cv_multinomialNB.pkl
    2,3M       cv_randomForest.pkl
    514        labelsEncodedNB.pkl
    514        labelsEncodedRf.pkl
    256K       model_multinomialNB.pkl
    106M       model_randomForest.pkl

El archivo serializado `model_multinomialNB.pkl` tiene un tamaño de 256KB que corresponde a la técnica *Miultinomial Naive Bayes*, y el archivo `model_randomForest.pkl` de la técnica *Random Forest* tiene un tamaño de 106MB.  El modelo generado por random Forest es como 40 veces más grande y esto se debe a que durante el entrenamiento para el *Random Forest* se usaron un total de 80 árboles binarios que tomo un tiempo considerablemente mayor en entrenar que la técnica *Multinomial Naive Bayes*.

### Cargando los modelos exportados

Ahora vamos a cargar estos modelos generados **en otro notebook** jupyter, este se puede revisar por completo:

> [jupyter notebook: debian-2015-2019-model-application.ipynb](https://gitlab.com/strysg/ejercicios-data-science/-/blob/master/curso4/proyecto/NaiveBayes/debian-2015-2019-model-application.ipynb)

En las primeras partes volvemos a usar pickle para importar los modelos guardados.

    :::python
    # cargando los modelos y otros datos generados anteriormente
    import pickle
    model_multinomialNB = pickle.load(open('./model_multinomialNB.pkl', 'rb'))
    cv_multinomialNB = pickle.load(open('./cv_multinomialNB.pkl', 'rb'))
    labelsEncodedNB = pickle.load(open('./labelsEncodedNB.pkl', 'rb'))

    model_randomForest = pickle.load(open('./model_randomForest.pkl', 'rb'))
    cv_randomForest = pickle.load(open('./cv_randomForest.pkl', 'rb'))
    labelsEncodedRf = pickle.load(open('lablesEncodedRf.pkl', 'rb'))

### Aplicando mensajes y generando nuevas predicciones

Se definen algunos mensajes de texto nuevos y los unimos en una lista python:

    :::python
    m1 = "We are looking for a GNU/Linux System Administrator in Charlotte, North Carolina. Other locations near the EST timezone will"
    m2 = '''Traceback (most recent call last): 
    File ""/tmp/autopkgtest-lxc.5a99fnj6/downtmp/autopkgtest_tmp/tests/test_core/test_sequence.py'''
    m3 = '''Actually, the GPL only mandates stating *who* made changes and *when*,but not *what* changed. As I see, 
    just adding something like ""Modified by John Doe on 2019.07.25"" on the comment header where the GPL copyright notice lies 
    would suffice"'''
    m4 = '''E: pybuild pybuild:336: test: plugin distutils failed with: exit code=1: 
    cd /build/snakemake-4.8.0/.pybuild/cpython3_3.6_snakemake/build; 
    python3.6 -m nose tests dh_auto_test: pybuild --test --test-nose -i python{version} -p 3.6 returned exit code 13 make: 
    *** [debian/rules:17: build] Error 25 dpkg-buildpackage: error: debian/rules build subprocess returned exit status 2 I: 
    copying local configuration E: Failed autobuilding of package 
    I: user script /var/cache/pbuilder/build/cow.11501/tmp/hooks/C99_failed_build starting root:/# echo $PATH'''
    m5 = """I'd take option 2.There ought to be no packages using python-dput (like, srly…), and I see no real reason for anybody
     to keep using the py2 version after the switch."""
    m6 = """Nothing can be done without previous voting phase, we are talking about somewhat important
     issues that must be taken into considereation before finally giving them a certain direction. Regards BlueBon."""
    m7 = """I would be happy to teach anyone, including you, how to sync between weblate and salsa 
    (which mostly involve 'git pull weblate master && git push' with a few clicks on the Weblate admin page to reduce 
    the chance of any git conflict in between)"""

	# uniendo
    testMsjs = pd.Series([m1,m2,m3,m4,m5,m6,m7])

Ahora agregamos las palabras nuevas a las matrices de conteo de palabras del entrenamiento anterior como se muestra en [[1]](https://towardsdatascience.com/develop-a-nlp-model-in-python-deploy-it-with-flask-step-by-step-744f3bdd7776):

    :::python
    vect = cv_multinomialNB.transform(testMsjs).toarray()

Usamos el modelo para generar nuevas predicciones:

    :::python
    predictions = model_multinomialNB.predict(vect)

Finalmente vamos a ver a qué categorías corresponden las predicciones.

    :::python
	# mostrando resultados
    for i in range(len(predictions)):
        print('m' + str(i+1), '-->', labelsEncodedNB[predictions[i]])
		
Que muestra:

    m1 --> debian-mentors
    m2 --> debian-python
    m3 --> debian-legal
    m4 --> debian-python
    m5 --> debian-python
    m6 --> debian-vote
    m7 --> debian-science

Haciendo lo mismo con el modelo de la técnica *Random Forest*:

    :::python
    vect = cv_randomForest.transform(testMsjs).toarray()
    predictions = model_randomForest.predict(vect)

    for i in range(len(predictions)):
        print('m' + str(i+1), '-->', labelsEncodedRf[predictions[i]])
		
Resulta:		
		
    m1 --> debian-mentors
    m2 --> debian-mentors
    m3 --> debian-legal
    m4 --> debian-python
    m5 --> debian-python
    m6 --> debian-mentors
    m7 --> debian-mentors

### Comentarios finales

Ambos modelos han clasificado bien la mayoría de los mensajes de pruebas, con algunas diferencias:

  * El mensaje m2 que es un texto con un mensaje de error de python, lo ha clasificado correctamente *Naive Bayes* pero no *Random Forest*
  * El mensaje m6 que es texto que habla de la necesidad de un proceso de votación antes de decidir algo, lo ha clasificado correctamente el *Naive Bayes* pero no *Random Forest*.
  * El mensaje m7 se parece más a un mensaje de alguien que se ofrece a enseñar algo, la técnica *Random Forest* lo ha clasificado correctamente en la categoría *debian-mentors*, y la técnica *Naive Bayes* lo considera de la categoría *debian-science*.
  
#### Usando mas allá

Lo bueno es que ya tenemos la forma sencilla de importar en cualquier otro programa los modelos generados en el entrenamiento anterior.

Las aplicaciones son muchas por ejemplo como se muestra en [[1]](https://towardsdatascience.com/develop-a-nlp-model-in-python-deploy-it-with-flask-step-by-step-744f3bdd7776) se puede desplegar una aplicación web con un formulario donde se introduzca texto y este es clasificado como *spam* o *no spam*. También se podría aplicar a un programa clasificador automático de mensajes cuando alguien escribe un correo electrónico cuando en caso de detectar un mensaje que no pertenece a la categoría a la que se esta escribiendo, el programa le avise que podría estar enviando su mensaje a una categoría equivocada.

La clasificación de textos es un área de estudio del aprendizaje automático muy interesante y estas técnicas que usamos demuestran ser muy útiles. Seguramente existen otras técnicas y la combinación de estas que se ajustan a distintos casos.

### Referencias

1. [https://towardsdatascience.com/develop-a-nlp-model-in-python-deploy-it-with-flask-step-by-step-744f3bdd7776](https://towardsdatascience.com/develop-a-nlp-model-in-python-deploy-it-with-flask-step-by-step-744f3bdd7776)
